package day29.ioFile;

import java.io.*;

/**
 * IOtest类用于演示和测试Java IO流的各种功能
 * Java IO流是Java中用于处理输入输出操作的核心机制
 * 本类涵盖了字节流、字符流、缓冲流和对象流的完整测试示例
 * 
 * Java IO流的分类：
 * 1. 按数据类型分类：
 *    - 字节流：处理二进制数据，适用于所有类型的文件
 *    - 字符流：专门处理文本数据，自动处理字符编码转换
 * 2. 按流向分类：
 *    - 输入流：从数据源读取数据到程序
 *    - 输出流：从程序写入数据到目标
 * 3. 按功能分类：
 *    - 节点流：直接与数据源或目标连接的流
 *    - 处理流：对其他流进行包装，提供增强功能的流
 */
public class IOtest {
    /**
     * main方法是Java程序的入口点
     * 在此方法中，我们创建IOtest实例并依次测试各种IO流功能
     * 程序执行流程：
     * 1. 打印测试标题
     * 2. 依次测试字节流、字符流、缓冲流和对象流
     * 3. 每个测试方法独立运行，互不影响
     * 
     * @param args 命令行参数数组，本程序未使用
     */
    public static void main(String[] args) {
        // 创建IOtest类的实例，用于调用各种测试方法
        // 这种方式使得测试方法可以访问实例变量和方法
        IOtest ioTest = new IOtest();
        
        // 打印测试标题，使用特殊符号增强可读性
        // 这是程序的开始标志，向用户表明即将进行IO流功能测试
        System.out.println("=== Java IO流基本功能测试 ===\n");
        
        // 测试字节流功能 - FileInputStream 和 FileOutputStream
        // 字节流用于处理二进制数据，如图片、音频、视频等文件
        // 每次读写一个字节(8位)，适用于所有类型的文件
        System.out.println("1. 测试字节流 - 文件复制:");
        // 调用testByteStream方法执行字节流测试
        ioTest.testByteStream();
        
        // 测试字符流功能 - FileReader 和 FileWriter
        // 字符流专门用于处理文本数据，每次读写一个字符(16位)
        // 自动处理字符编码转换，更适合处理文本文件
        System.out.println("\n2. 测试字符流 - 文本读写:");
        // 调用testCharacterStream方法执行字符流测试
        ioTest.testCharacterStream();
        
        // 测试缓冲流功能 - BufferedInputStream 和 BufferedOutputStream
        // 缓冲流通过在内存中创建缓冲区来提高IO操作的效率
        // 减少实际的磁盘读写次数，大幅提升性能
        System.out.println("\n3. 测试缓冲流 - 提高读写效率:");
        // 调用testBufferedStream方法执行缓冲流测试
        ioTest.testBufferedStream();
        
        // 测试对象流功能 - ObjectOutputStream 和 ObjectInputStream
        // 对象流用于实现对象的序列化和反序列化
        // 可以将Java对象转换为字节序列存储到文件或网络传输，之后再恢复为原对象
        System.out.println("\n4. 测试对象流 - 对象序列化:");
        // 调用testObjectStream方法执行对象流测试
        ioTest.testObjectStream();
    }
    
    /**
     * 测试字节流 - FileInputStream 和 FileOutputStream
     * 字节流用于处理二进制数据，如图片、音频、视频等文件
     * 每次读写一个字节(8位)，适用于所有类型的文件
     * 
     * 字节流的核心类：
     * 1. InputStream(抽象类) - 字节输入流的基类
     *    - FileInputStream - 从文件读取字节
     *    - ByteArrayInputStream - 从字节数组读取字节
     *    - BufferedInputStream - 带缓冲功能的字节输入流
     * 2. OutputStream(抽象类) - 字节输出流的基类
     *    - FileOutputStream - 向文件写入字节
     *    - ByteArrayOutputStream - 向字节数组写入字节
     *    - BufferedOutputStream - 带缓冲功能的字节输出流
     * 
     * 字节流的特点：
     * 1. 适用于所有类型的数据（文本、图片、音频、视频等）
     * 2. 以字节为单位进行读写操作
     * 3. 不会自动处理字符编码转换
     * 4. 读写效率相对较低（特别是逐字节读写时）
     * 5. 需要手动管理资源（打开和关闭流）
     */
    public void testByteStream() {
        // 使用try-catch语句块处理可能发生的IO异常
        // IOException是所有IO操作可能抛出的异常的基类
        try {
            // 创建FileOutputStream对象，用于向文件写入字节数据
            // FileOutputStream构造方法参数是文件路径，如果文件不存在会自动创建
            // 如果文件已存在，默认会覆盖原文件内容
            FileOutputStream fos = new FileOutputStream("test_byte.txt");
            // 定义要写入文件的字符串数据
            // 包含数字、换行符、英文字符等多种数据类型
            String data = "5201314\n  Hello, this is a test file for byte stream.";
            // 将字符串转换为字节数组并写入文件
            // getBytes()方法使用平台默认字符编码将字符串转换为字节数组
            // write(byte[] b)方法将整个字节数组写入输出流
            fos.write(data.getBytes());
            // 关闭输出流，释放系统资源
            // 这是非常重要的步骤，确保数据被完全写入文件并释放相关资源
            // 如果不关闭流，可能导致数据丢失或资源泄露
            fos.close();
            // 打印成功信息，告知用户测试文件创建成功
            System.out.println("  创建测试文件 test_byte.txt 成功");
            
            // 创建FileInputStream对象，用于从文件读取字节数据
            // FileInputStream构造方法参数是文件路径，文件必须存在否则抛出FileNotFoundException
            FileInputStream fis = new FileInputStream("test_byte.txt");
            // 打印文件内容提示信息
            System.out.print("  文件内容: ");
            // 定义变量存储读取到的字节数据，返回-1表示文件末尾
            // int类型可以存储一个字节的值(0-255)以及特殊值-1表示文件结束
            int byteData;
            // 循环读取文件中的每个字节，直到文件末尾
            // read()方法每次读取一个字节，返回该字节的值，文件结束时返回-1
            // 这种逐字节读取的方式效率较低，仅用于演示目的
            while ((byteData = fis.read()) != -1) {
                // 将读取到的字节转换为字符并打印
                // 通过强制类型转换将int类型的字节值转换为char类型
                // 这种方式仅适用于ASCII字符，对于中文等多字节字符会出现乱码
                System.out.print((char) byteData);
            }
            // 关闭输入流，释放系统资源
            // 与输出流一样，输入流也需要正确关闭以释放资源
            fis.close();
            // 打印换行符，美化输出格式
            System.out.println();
            
            // 创建新的输入输出流用于文件复制操作
            // 这里演示如何使用字节流进行文件复制
            FileInputStream fis2 = new FileInputStream("test_byte.txt");
            FileOutputStream fos2 = new FileOutputStream("test_byte_copy.txt");
            
            // 创建字节数组作为缓冲区，提高读写效率
            // 缓冲区大小通常设置为1024、2048、4096等2的幂次方
            // 较大的缓冲区可以减少IO操作次数，提高效率
            byte[] buffer = new byte[1024];
            // 定义变量存储每次实际读取的字节数
            // read(byte[] b)方法返回实际读取的字节数，文件结束时返回-1
            int length;
            // 循环读取源文件数据并写入目标文件，直到源文件读取完毕
            // 使用缓冲区可以显著提高IO操作效率
            while ((length = fis2.read(buffer)) > 0) {
                // 将缓冲区中的数据写入目标文件，指定起始位置和长度
                // write(byte[] b, int off, int len)方法从缓冲区的off位置开始写入len个字节
                // 这里从位置0开始写入length个字节
                fos2.write(buffer, 0, length);
            }
            
            // 关闭输入输出流
            // 按照先打开后关闭的原则，后打开的流先关闭
            fis2.close();
            fos2.close();
            // 打印文件复制成功信息
            System.out.println("  文件复制成功: test_byte.txt -> test_byte_copy.txt");
            
            // 清理测试过程中创建的临时文件
            // delete()方法删除文件，返回true表示删除成功，false表示删除失败
            new File("test_byte.txt").delete();
            new File("test_byte_copy.txt").delete();
            
        } catch (IOException e) {
            // 捕获并打印IO异常信息
            // printStackTrace()方法将异常的堆栈跟踪信息输出到标准错误流
            // 这有助于调试和定位问题
            e.printStackTrace();
        }
    }
    
    /**
     * 测试字符流 - FileReader 和 FileWriter
     * 字符流专门用于处理文本数据，每次读写一个字符(16位)
     * 自动处理字符编码转换，更适合处理文本文件
     * 
     * 字符流的核心类：
     * 1. Reader(抽象类) - 字符输入流的基类
     *    - FileReader - 从文件读取字符
     *    - BufferedReader - 带缓冲功能的字符输入流
     *    - InputStreamReader - 从字节流读取字符（字符编码转换）
     * 2. Writer(抽象类) - 字符输出流的基类
     *    - FileWriter - 向文件写入字符
     *    - BufferedWriter - 带缓冲功能的字符输出流
     *    - OutputStreamWriter - 向字节流写入字符（字符编码转换）
     * 
     * 字符流的特点：
     * 1. 专门用于处理文本数据
     * 2. 以字符为单位进行读写操作（Java中字符为16位）
     * 3. 自动处理字符编码转换（默认使用系统字符编码）
     * 4. 对于文本文件处理更加方便和高效
     * 5. 读写中文等多字节字符不会出现乱码问题
     */
    public void testCharacterStream() {
        // 使用try-catch语句块处理可能发生的IO异常
        try {
            // 创建FileWriter对象，用于向文件写入字符数据
            // FileWriter构造方法参数是文件路径，如果文件不存在会自动创建
            // 如果文件已存在，默认会覆盖原文件内容
            FileWriter writer = new FileWriter("test_char.txt");
            // 定义要写入的文本内容，包含中英文混合数据
            // 这种混合内容可以测试字符流对不同字符的处理能力
            String text = "这是字符流测试。\nThis is character stream test.\n中文和英文混合测试。";
            // 将文本内容写入文件
            // write(String str)方法将字符串写入文件
            // 字符流会自动处理字符编码转换
            writer.write(text);
            // 关闭写入流
            // 关闭流会自动刷新缓冲区，确保数据被完全写入文件
            writer.close();
            // 打印成功信息，告知用户文本文件创建和写入成功
            System.out.println("  创建并写入文本文件 test_char.txt 成功");
            
            // 创建FileReader对象用于读取文件字符数据
            // FileReader构造方法参数是文件路径，文件必须存在否则抛出FileNotFoundException
            FileReader reader = new FileReader("test_char.txt");
            // 使用BufferedReader包装FileReader，提供按行读取功能
            // BufferedReader提供了readLine()方法，可以按行读取文本文件
            // 这是处理文本文件的常用模式
            BufferedReader bufferedReader = new BufferedReader(reader);
            // 打印文件内容提示信息
            System.out.println("  文件内容:");
            // 定义变量存储读取到的每一行文本
            // readLine()方法返回一行文本，文件结束时返回null
            String line;
            // 循环按行读取文件内容，直到文件末尾(返回null)
            // 按行读取是处理文本文件的最常见方式
            while ((line = bufferedReader.readLine()) != null) {
                // 打印读取到的每一行内容，前面添加缩进
                // 这种格式化输出使内容更易读
                System.out.println("    " + line);
            }
            // 关闭BufferedReader，会自动关闭内部的FileReader
            // 这是处理流的常用模式，关闭外层流会自动关闭内层流
            bufferedReader.close();
            
            // 清理测试文件
            // delete()方法删除文件，返回true表示删除成功，false表示删除失败
            new File("test_char.txt").delete();
            
        } catch (IOException e) {
            // 捕获并打印IO异常信息
            e.printStackTrace();
        }
    }
    
    /**
     * 测试缓冲流 - BufferedInputStream 和 BufferedOutputStream
     * 缓冲流通过在内存中创建缓冲区来提高IO操作的效率
     * 减少实际的磁盘读写次数，大幅提升性能
     * 
     * 缓冲流的工作原理：
     * 1. 缓冲输入流：
     *    - 创建固定大小的内存缓冲区（默认8192字节）
     *    - 一次性从数据源读取大量数据到缓冲区
     *    - 后续的读取操作直接从缓冲区获取数据
     *    - 缓冲区数据用完后再从数据源读取下一批数据
     * 2. 缓冲输出流：
     *    - 创建固定大小的内存缓冲区（默认8192字节）
     *    - 写入操作先将数据存储在缓冲区中
     *    - 缓冲区满或调用flush()方法时才将数据写入目标
     *    - 减少实际的磁盘写入次数
     * 
     * 缓冲流的优势：
     * 1. 显著提高IO操作性能
     * 2. 减少系统调用次数
     * 3. 降低磁盘读写频率
     * 4. 提高程序整体执行效率
     */
    public void testBufferedStream() {
        // 使用try-catch语句块处理可能发生的IO异常
        try {
            // 创建一个较大的测试文件用于性能对比
            // FileWriter用于创建和写入文本文件
            FileWriter writer = new FileWriter("test_large.txt");
            // 写入1000行测试数据
            // 通过循环写入大量数据，便于观察缓冲流的性能优势
            for (int i = 0; i < 1000; i++) {
                // 每行写入包含行号的中文测试数据
                // 这种数据便于验证文件复制的正确性
                writer.write("这是第" + i + "行测试数据，用于测试缓冲流的性能。\n");
            }
            // 关闭写入流，确保数据被完全写入文件
            writer.close();
            
            // 测试不使用缓冲流的文件复制性能
            // 记录开始时间，用于计算执行耗时
            long startTime = System.currentTimeMillis();
            // 创建文件输入流，用于读取源文件
            FileInputStream fis = new FileInputStream("test_large.txt");
            // 创建文件输出流，用于写入目标文件
            FileOutputStream fos = new FileOutputStream("test_large_copy1.txt");
            
            // 逐字节读取和写入（效率较低）
            // 这种方式每次读写都会触发系统调用，效率很低
            int data;
            // 循环读取每个字节直到文件末尾
            while ((data = fis.read()) != -1) {
                // 将读取的字节写入目标文件
                fos.write(data);
            }
            
            // 关闭流并计算耗时
            // 按照先打开后关闭的原则，后打开的流先关闭
            fis.close();
            fos.close();
            // 记录结束时间
            long endTime = System.currentTimeMillis();
            // 打印不使用缓冲流的执行耗时
            System.out.println("  不使用缓冲流耗时: " + (endTime - startTime) + "毫秒");
            
            // 测试使用缓冲流的文件复制性能
            // 记录开始时间
            startTime = System.currentTimeMillis();
            // 创建缓冲输入流，包装原始文件输入流
            // BufferedInputStream构造方法接收InputStream参数
            // 默认缓冲区大小为8192字节
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test_large.txt"));
            // 创建缓冲输出流，包装原始文件输出流
            // BufferedOutputStream构造方法接收OutputStream参数
            // 默认缓冲区大小为8192字节
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test_large_copy2.txt"));
            
            // 使用缓冲流逐字节读取和写入（效率较高）
            // 虽然仍是逐字节操作，但由于缓冲机制，实际性能大幅提升
            int bufferedData;
            // 循环读取每个字节直到文件末尾
            while ((bufferedData = bis.read()) != -1) {
                // 将读取的字节写入目标文件
                bos.write(bufferedData);
            }
            
            // 关闭缓冲流并计算耗时
            // 关闭缓冲流会自动刷新缓冲区，确保数据被完全写入
            bis.close();
            bos.close();
            // 记录结束时间
            endTime = System.currentTimeMillis();
            // 打印使用缓冲流的执行耗时
            System.out.println("  使用缓冲流耗时: " + (endTime - startTime) + "毫秒");
            
            // 清理所有测试文件
            // delete()方法删除文件，返回true表示删除成功，false表示删除失败
            new File("test_large.txt").delete();
            new File("test_large_copy1.txt").delete();
            new File("test_large_copy2.txt").delete();
            
        } catch (IOException e) {
            // 捕获并打印IO异常信息
            e.printStackTrace();
        }
    }
    
    /**
     * 测试对象流 - ObjectOutputStream 和 ObjectInputStream
     * 对象流用于实现对象的序列化和反序列化
     * 可以将Java对象转换为字节序列存储到文件或网络传输，之后再恢复为原对象
     * 
     * 对象序列化的作用：
     * 1. 对象持久化：将对象状态保存到文件或数据库中
     * 2. 网络传输：在网络上传输Java对象
     * 3. 深拷贝：创建对象的完整副本
     * 4. 缓存：将对象状态缓存以提高性能
     * 
     * 序列化的条件：
     * 1. 对象所属的类必须实现Serializable接口
     * 2. 类中的所有非瞬态(非transient)实例变量都必须可序列化
     * 3. 如果有自定义序列化需求，可以实现writeObject和readObject方法
     * 
     * serialVersionUID的作用：
     * 1. 版本控制：确保序列化和反序列化的类版本一致
     * 2. 兼容性检查：防止因类结构变化导致的反序列化失败
     * 3. 建议显式声明，避免因编译器自动生成导致的不一致
     */
    public void testObjectStream() {
        // 使用try-catch语句块处理可能发生的IO异常和类未找到异常
        // ClassNotFoundException是反序列化时可能抛出的异常
        try {
            // 创建用于测试的Person对象
            // Person类实现了Serializable接口，可以被序列化
            Person person = new Person("张三", 25, "zhangsan@example.com");
            
            // 创建对象输出流，包装文件输出流
            // ObjectOutputStream用于将对象序列化为字节流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"));
            // 将Person对象序列化并写入文件
            // writeObject方法将对象转换为字节序列并写入输出流
            // 这个过程包括对象图遍历、类型信息记录等复杂操作
            oos.writeObject(person);
            // 关闭对象输出流
            // 关闭流会自动刷新缓冲区，确保对象被完全写入文件
            oos.close();
            // 打印序列化成功信息
            System.out.println("  对象序列化成功: " + person);
            
            // 创建对象输入流，包装文件输入流
            // ObjectInputStream用于从字节流反序列化对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"));
            // 从文件中读取字节序列并反序列化为Person对象
            // readObject方法读取字节序列并重建对象
            // 这个过程包括类型检查、对象创建、字段赋值等操作
            Person deserializedPerson = (Person) ois.readObject();
            // 关闭对象输入流
            ois.close();
            // 打印反序列化成功信息
            System.out.println("  对象反序列化成功: " + deserializedPerson);
            
            // 清理序列化生成的文件
            // delete()方法删除文件，返回true表示删除成功，false表示删除失败
            new File("person.ser").delete();
            
        } catch (IOException | ClassNotFoundException e) {
            // 捕获IO异常和类未找到异常
            // 使用多重catch语句可以同时处理多种异常
            e.printStackTrace();
        }
    }
    
    /**
     * 测试用的简单Person类
     * 实现Serializable接口以支持序列化
     * 序列化是将对象转换为字节序列的过程
     * 
     * Serializable接口的特点：
     * 1. 标记接口：不包含任何方法，仅作为标记使用
     * 2. 启用序列化：实现该接口的类可以被序列化
     * 3. 递归序列化：类中的所有引用类型字段也必须可序列化
     * 4. 默认序列化：自动序列化所有非transient和非static字段
     * 
     * 序列化的注意事项：
     * 1. serialVersionUID：建议显式声明以确保版本兼容性
     * 2. transient关键字：标记不需要序列化的字段
     * 3. static字段：不会被序列化，因为它们属于类而非实例
     * 4. 安全性：敏感信息不应被序列化
     */
    static class Person implements Serializable {
        // 序列化版本号，用于验证序列化对象的发送方和接收方是否为同一个版本
        // 如果序列化时和反序列化时的版本号不一致，会抛出InvalidClassException
        // 建议显式声明，避免因编译器自动生成导致的不一致
        private static final long serialVersionUID = 1L;
        // 姓名属性，String类型天然可序列化
        private String name;
        // 年龄属性，基本数据类型int天然可序列化
        private int age;
        // 邮箱属性，String类型天然可序列化
        private String email;
        
        // 构造方法，初始化Person对象的属性
        // 构造方法不会被序列化，反序列化时会调用无参构造方法创建对象
        // 然后直接恢复字段值，不会调用构造方法
        public Person(String name, int age, String email) {
            // 使用this关键字区分成员变量和参数
            this.name = name;
            this.age = age;
            this.email = email;
        }
        
        // 重写toString方法，用于输出对象信息
        // toString方法在打印对象时会被自动调用
        // 提供有意义的对象信息有助于调试和日志记录
        @Override
        public String toString() {
            // 返回包含对象所有重要信息的字符串
            // 格式化输出使信息更易读
            return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
        }
    }
}